#include "log4x.h"
#include "semaphore.h"
#include "file.h"
#include "stream.h"
#include "thread.h"
#include "autolock.h"
#include "version.h"
#include "isys.h"
#include "itime.h"
#include "xtime.h"
#include "xprocess.h"
#include "str.h"
#include "fs.h"
#include <errno.h>
#include <stdarg.h>
#include <sys/timeb.h>
#include <map>
#include <queue>
#include <algorithm>

/* base macro. */
#define LOG_STREAM(key, level, func, file, line, sync, log) \
do \
{ \
    if (0 == prepush(key, level)) \
    {\
        log4x_t * __log = make(key, level, func, file, line); \
		char buf[1024*4] = {0}; \
        Stream __ss(buf, 1024*4); \
        __ss << log; \
        __log->text = buf; \
        push(__log, sync); \
    } \
} while (0)

/* fast macro. */
#define LOG_TRACE(key, log) LOG_STREAM(key, LOG_LEVEL_TRACE, __FUNCTION__, __FILE__, __LINE__, false, log)
#define LOG_DEBUG(key, log) LOG_STREAM(key, LOG_LEVEL_DEBUG, __FUNCTION__, __FILE__, __LINE__, false, log)
#define LOG_INFO(key,  log) LOG_STREAM(key, LOG_LEVEL_INFO,  __FUNCTION__, __FILE__, __LINE__, false, log)
#define LOG_WARN(key,  log) LOG_STREAM(key, LOG_LEVEL_WARN,  __FUNCTION__, __FILE__, __LINE__, false, log)
#define LOG_ERROR(key, log) LOG_STREAM(key, LOG_LEVEL_ERROR, __FUNCTION__, __FILE__, __LINE__, false, log)
#define LOG_FATAL(key, log) LOG_STREAM(key, LOG_LEVEL_FATAL, __FUNCTION__, __FILE__, __LINE__, true , log)

/* super macro. */
#define LOGT(log) LOG_TRACE("main", log )
#define LOGD(log) LOG_DEBUG("main", log )
#define LOGI(log) LOG_INFO ("main", log )
#define LOGW(log) LOG_WARN ("main", log )
#define LOGE(log) LOG_ERROR("main", log )
#define LOGF(log) LOG_FATAL("main", log )

namespace log4x
{
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vestimate(const char *format, va_list ap);
int vpush(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, va_list ap);

static const char *const log4x_level[] =
{
    "TRA",
    "DBG",
    "INF",
    "WAR",
    "ERR",
    "FAT",
};

#ifdef WIN32
const static WORD log4x_color[] =
{
    0,
    FOREGROUND_GREEN,
    FOREGROUND_BLUE | FOREGROUND_GREEN,
    FOREGROUND_GREEN | FOREGROUND_RED,
    FOREGROUND_RED,
    FOREGROUND_RED | FOREGROUND_BLUE
};
#else
const static char *log4x_color[] =
{
    "\e[0m",
    "\e[32m",                                              /* green */
    "\e[34m\e[1m",                                         /* hight blue */
    "\e[33m",                                              /* yellow */
    "\e[31m",                                              /* red */
    "\e[35m"
};
#endif

/* log4x_t */
struct log4x_t
{
    std::string    key;
    int            level;

    time_t         time;
    int            msec;
    long           tid;

    std::string    pts;
    std::string    mode;
    std::string    pos;
    std::string    text;
};


typedef std::map<time_t, std::string> log4x_history_t;
struct log4x_value_t
{
    bool           enable;
    std::string    key;
    std::string    path;
    int            level;

    bool           display;
    bool           outfile;
    bool           monthdir;
    long           limitsize;                              /* limit file's size. Mb */
    bool           fileline;
    long           reserve;                                /* history logs reserve time. second. */
    int            filecount;                              /* history logs reserver file count */
    bool           procfile;                               /* printf to process file */

    bool           fromfile;                               /* is from log.ini file */
    File           fhandle;                                /* file handle. */
    time_t         ftime;                                  /* file create time */
    size_t         flen;                                   /* current file length */
    int            findex;                                 /* rolling file index */

    int            flushes;                                /* logs will be flushed */

    log4x_history_t history;                               /* history log files */

    Mutex           mtx;

    log4x_value_t()
    {
        enable    = false;
        fromfile  = false;
        procfile  = true;
        path      = LOG4X_DEFAULT_PATH;
        level     = LOG4X_DEFAULT_LEVEL;
        display   = LOG4X_DEFAULT_DISPLAY;
        outfile   = LOG4X_DEFAULT_OUTFILE;
        monthdir  = LOG4X_DEFAULT_MONTHDIR;
        limitsize = LOG4X_DEFAULT_LIMITSIZE;
        fileline  = LOG4X_DEFAULT_FILELINE;
        reserve   = LOG4X_DEFAULT_RESERVE;
        filecount = LOG4X_DEFAULT_RESERVE_FILES;

        ftime     = 0;
        findex    = 0;
        flen      = 0;

        flushes   = 0;
    }
};

struct log4x_process_t
{
    std::string    name;                                   /* proess name. */
    std::string    pid;                                    /* proess id. */

    std::string    path;                                   /* proess log file full path */
    bool           monthdir;
    long           limitsize;                              /* limit file's size. Mb */
    long           reserve;                                /* history logs reserve time. second. */
    int            filecount;                              /* history logs reserver file count */
    bool           outputide;                              /* display logs on ide debug windows */
    bool           outputsyn;                              /* output logs synchronous */

    File           fhandle;                                /* file handle. */
    time_t         ftime;                                  /* file create time */
    size_t         flen;                                   /* current file length */
    int            findex;                                 /* rolling file index */
    int            flushes;                                /* logs will be flushed */

    log4x_history_t history;                               /* history log files */

    Mutex           mtx;

    log4x_process_t()
    {
        string cmdline  = os::process()->cmdline();
        string procArgv = cmdline;
        string argv;
        string::size_type pos = cmdline.find("--instance=");
        if (string::npos == pos)
        {
            argv = "";
        }
        else
        {
            argv.assign(procArgv, pos, string::npos);
            argv = str::gsub(argv, " ", "");
            argv = str::gsub(argv, "\"", "");
            argv = str::gsub(argv, "/", "");
            argv = str::gsub(argv, "\\", "");
            argv = str::gsub(argv, "--instance=", "");
        }
        
        name = process::pname();
        name  = name.find(".exe") ? fs::stem(name) : name;
        if (!argv.empty())
        {
            name += "_" + argv;
        }
        pid       = str::format("%06d", process::pid());

        path      = LOG4X_DEFAULT_PATH;
        monthdir  = LOG4X_DEFAULT_MONTHDIR;
        limitsize = LOG4X_DEFAULT_LIMITSIZE;
        reserve   = LOG4X_DEFAULT_RESERVE;
        filecount = LOG4X_DEFAULT_RESERVE_FILES;
        outputide = true;
        outputsyn = false;

        ftime     = 0;
        findex    = 0;
        flen      = 0;
        flushes   = 0;
    }
};

typedef std::map<std::string, log4x_value_t*> log4x_keys_t;
typedef std::deque<log4x_t *>                 log4x_queue_t;
typedef std::vector<log4x_t *>                log4x_pool_t;
/* typedef std::list<log4x_t *>                 log4x_pool_t; */

/**
 * log4x
 */
class log4x : public Thread, public ilog4x
{
public:
    log4x();
    virtual ~log4x();

    int                vpush(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, va_list ap);
protected:
    virtual int        config(const char * path);
    virtual int        configFromString(const char * str);

    virtual int        start();
    virtual void       stop();

    virtual log4x_t  * make(const char * key, int level, const char * func, const char * file, int line);
    virtual void       free(log4x_t * log);
    virtual int        prepush(const char * key, int level);
    virtual int        push(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, ...);
    virtual int        push(log4x_t * log, bool sync);

    virtual int        enable(const char * key, bool enable);
    virtual int        setPath(const char * key, const char * path);
    virtual int        setLevel(const char * key, int level);
    virtual int        setFileLine(const char * key, bool enable);
    virtual int        setDisplay(const char * key, bool enable);
    virtual int        setOutFile(const char * key, bool enable);
    virtual int        setLimitsize(const char * key, unsigned int limitsize);
    virtual int        setMonthdir(const char * key, bool enable);
    virtual int        setReserve(const char * key, unsigned int sec);
    virtual int        setProcFile(const char * key, bool enable);

    virtual void       setAutoUpdate(int interval);
    virtual int        update();
    virtual bool       isff(const char * key);
protected:
    virtual void       run();

protected:
    log4x_t          * pop();
    int                parse(const std::string &str);
    int                parse(const std::string &str, log4x_keys_t &values);
    void               parse(const std::string &line, int number, std::string &key, log4x_keys_t &values);

    int                split(const std::string &str, const std::string &delimiter, std::pair<std::string, std::string> &pair);
    void               trim(std::string &str, const std::string &ignore = std::string("\r\n\t "));

    int                openKeyFile(log4x_t * log);
    int                openProcFile(log4x_t * log);
    void               close(const std::string &key);

    void               printNonBlock(const char* fmt, ...);
    inline void        showColorText(const char * text, int level);
    inline int         history(const std::string &path, const std::string &key, log4x_history_t &files);

private:
    Semaphore         _sem;
    int               _hotInterval;

    long              _totalCount;
    long              _totalBytes;
    long              _totalPush;
    long              _totalPop;
    int               _activeCount;

    std::string       _path;

    unsigned long     _checksum;

    log4x_keys_t      _keys;
    Mutex             _keys_mtx;

    log4x_queue_t     _logs;
    Mutex             _logs_mtx;

    log4x_queue_t     _cache;
    Mutex             _cache_mtx;

    log4x_pool_t      _pool;
    Mutex             _pool_mtx;

    Mutex             _show_mtx;
    log4x_process_t   _proc;
};

log4x::log4x()
{
    _hotInterval = 30;
    _totalCount  = 0;
    _totalBytes  = 0;
    _totalPush   = 0;
    _totalPop    = 0;
    _activeCount = 0;
    _checksum    = 0;
}

log4x::~log4x()
{
    stop();
    for (std::map<std::string, log4x_value_t*>::iterator iter = _keys.begin(); iter != _keys.end(); ++iter)
    {
        delete iter->second;
    }
}

int
log4x::config(const char * path)
{
    do
    {
        if (!_path.empty())
        {
            printNonBlock("log4x has configured, the old config file: %s, the new config file: %s\n", _path.c_str(), path);
            return -1;
        }
        _path = path;

        File f;
        f.open(_path.c_str(), "rb");
        if (!f.isOpen())
        {
            printNonBlock("log4x load config file failed: %s\n", path);
            break;
        }

        if (0 == parse(f.readContent()))
        {
            return 0;
        }
    }
    while (0);

    {
        /* add the main key default */
        AutoLock locker(_keys_mtx);
        if (_keys.find("main") == _keys.end())
        {
            _keys["main"] = new log4x_value_t;

            _keys["main"]->enable = true;
            _keys["main"]->level  = LOG_LEVEL_TRACE;
            _keys["main"]->key    = "main";

            history(LOG4X_DEFAULT_PATH, "main", _keys["main"]->history);
        }
    }

    {
        AutoLock locker(_proc.mtx);
        history(_proc.path, "global", _proc.history);
    }

    return 0;
}

int
log4x::configFromString(const char * str)
{
    return parse(str);
}

int
log4x::parse(const std::string &str)
{
    unsigned long sum = 0;
    for (std::string::const_iterator iter = str.begin(); iter != str.end(); ++iter)
    {
        sum += (unsigned char) * iter;
    }

    if (sum == _checksum)
    {
        /* no changed */
        return 0;
    }

    _checksum = sum;

    /* parse ok, we copy it */
    {
        AutoLock locker(_keys_mtx);

        if (parse(str, _keys) < 0)
        {
            printNonBlock(" !!! !!! !!! !!!\r\n");
            printNonBlock(" !!! !!! log4x load config file error \r\n");
            printNonBlock(" !!! !!! !!! !!!\r\n");
            return -1;
        }

        for (std::map<std::string, log4x_value_t*>::iterator iter = _keys.begin(); iter != _keys.end(); ++iter)
        {
            history(iter->second->path, iter->first, iter->second->history);
        }
    }

    {
        AutoLock locker(_proc.mtx);
        history(_proc.path, "global", _proc.history);
    }
    return 0;
}

int
log4x::parse(const std::string &content, log4x_keys_t &values)
{
    std::string key;
    int curLine = 1;
    std::string line;
    std::string::size_type curPos = 0;

    if (content.empty())
    {
        return -1;
    }

    do
    {
        std::string::size_type pos = std::string::npos;
        for (std::string::size_type i = curPos; i < content.length(); ++i)
        {
            /* support linux/unix/windows LRCF */
            if (content[i] == '\r' || content[i] == '\n')
            {
                pos = i;
                break;
            }
        }

        line = content.substr(curPos, pos - curPos);
        parse(line, curLine, key, values);

        curLine++;

        if (pos == std::string::npos)
        {
            break;
        }

        curPos = pos + 1;
    }
    while (1);

    return 0;
}

void
log4x::parse(const std::string& line, int number, std::string & key, log4x_keys_t & values)
{
    /* std::cout << "line: " << line << "number: " << number << std::endl; */
    std::pair<std::string, std::string> kv;
    split(line, "=", kv);

    if (kv.first.empty())
    {
        return;
    }

    trim(kv.first);
    trim(kv.second);

    if (kv.first.empty() || kv.first.at(0) == '#')
    {
        return;
    }

    /* std::cout << "kv.first: " << kv.first << "\tkv.second: " << kv.second << std::endl; */
    if (kv.first.at(0) == '[')
    {
        trim(kv.first, "[]");

        key = kv.first;
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);
        /* std::cout << "key: " << key << std::endl; */

        if ("global" == key)
        {
            return;
        }

        std::map<std::string, log4x_value_t*>::iterator iter = values.find(key);
        if (iter == values.end())
        {
            values[key] = new log4x_value_t();
            values[key]->enable   = true;
            values[key]->fromfile = true;
            values[key]->key      = key;
        }
        else
        {
            printNonBlock("log4x configure warning: duplicate logger key:[%s] at line: %d \r\n", key.c_str(), number);
        }

        return;
    }

    trim(kv.first);
    trim(kv.second);

    do
    {
        std::transform(kv.first.begin(), kv.first.end(), kv.first.begin(), ::tolower);
        std::transform(kv.second.begin(), kv.second.end(), kv.second.begin(), ::tolower);

        if ("global" == key)
        {
            if (kv.first == "path")
            {
                _proc.path = kv.second;
            }
            else if (kv.first == "limitsize")
            {
                _proc.limitsize = atoi(kv.second.c_str());
            }
            else if (kv.first == "reserve")
            {
                _proc.reserve = atol(kv.second.c_str());
            }
            else if (kv.first == "filecount")
            {
                _proc.filecount = atol(kv.second.c_str());
            }
            else if (kv.first == "outputide")
            {
                if (kv.second == "false" || kv.second == "0")
                {
                    _proc.outputide = false;
                }
                else
                {
                    _proc.outputide = true;
                }
            }
            else if (kv.first == "outputsyn")
            {
                if (kv.second == "false" || kv.second == "0")
                {
                    _proc.outputsyn = false;
                }
                else
                {
                    _proc.outputsyn = true;
                }
            }
            break;
        }

        std::map<std::string, log4x_value_t*>::iterator iter = values.begin();
        for (iter = values.begin(); iter != values.end(); ++iter)
        {
            string k = iter->first;
            std::transform(k.begin(), k.end(), k.begin(), ::tolower);
            if (k == key)
            {
                break;
            }
        }

        if (iter == values.end())
        {
            printNonBlock("log4x configure warning: not found current logger name:[%s] at line:%d, key=%s, value=%s \r\n",
                          key.c_str(), number, kv.first.c_str(), kv.second.c_str());
            break;
        }

        /* path */
        if (kv.first == "path")
        {
            iter->second->path = kv.second;
        }

        /* level */
        else if (kv.first == "level")
        {
            if (kv.second == "trace" || kv.second == "all")
            {
                iter->second->level = LOG_LEVEL_TRACE;
            }
            else if (kv.second == "debug")
            {
                iter->second->level = LOG_LEVEL_DEBUG;
            }
            else if (kv.second == "info")
            {
                iter->second->level = LOG_LEVEL_INFO;
            }
            else if (kv.second == "warn" || kv.second == "warning")
            {
                iter->second->level = LOG_LEVEL_WARN;
            }
            else if (kv.second == "error")
            {
                iter->second->level = LOG_LEVEL_ERROR;
            }
            else if (kv.second == "fatal")
            {
                iter->second->level = LOG_LEVEL_FATAL;
            }
        }

        /* display */
        else if (kv.first == "display")
        {
            if (kv.second == "false" || kv.second == "0")
            {
                iter->second->display = false;
            }
            else
            {
                iter->second->display = true;
            }
        }

        /* output to file */
        else if (kv.first == "outfile")
        {
            if (kv.second == "false" || kv.second == "0")
            {
                iter->second->outfile = false;
            }
            else
            {
                iter->second->outfile = true;
            }
        }

        /* monthdir */
        else if (kv.first == "monthdir")
        {
            if (kv.second == "false" || kv.second == "0")
            {
                iter->second->monthdir = false;
            }
            else
            {
                iter->second->monthdir = true;
            }
        }

        /* limit file size */
        else if (kv.first == "limitsize")
        {
            iter->second->limitsize = atoi(kv.second.c_str());
        }

        /* display log in file line */
        else if (kv.first == "fileline")
        {
            if (kv.second == "false" || kv.second == "0")
            {
                iter->second->fileline = false;
            }
            else
            {
                iter->second->fileline = true;
            }
        }

        /* enable/disable one logger */
        else if (kv.first == "enable")
        {
            if (kv.second == "false" || kv.second == "0")
            {
                iter->second->enable = false;
            }
            else
            {
                iter->second->enable = true;
            }
        }

        /* set reserve time */
        else if (kv.first == "reserve")
        {
            iter->second->reserve = atol(kv.second.c_str());
        }

        /* set reserve file count*/
        else if (kv.first == "filecount")
        {
            iter->second->filecount = atol(kv.second.c_str());
        }
    }
    while (0);
}

int
log4x::split(const std::string &str, const std::string &delimiter, std::pair<std::string, std::string> &pair)
{
    std::string::size_type pos = str.find(delimiter.c_str());
    if (pos == std::string::npos)
    {
        pair = std::make_pair(str, "");
        return 0;
    }

    pair = std::make_pair(str.substr(0, pos), str.substr(pos + delimiter.length()));

    return 0;
}

void
log4x::trim(std::string &str, const std::string &ignore)
{
    if (str.empty())
    {
        return;
    }

    size_t length = str.length();
    size_t begin  = 0;
    size_t end    = 0;

    /* trim utf8 file bom */
    if (str.length() >= 3
            && (unsigned char)str[0] == 0xef
            && (unsigned char)str[1] == 0xbb
            && (unsigned char)str[2] == 0xbf)
    {
        begin = 3;
    }

    /* trim character */
    for (size_t i = begin ; i < length; i++)
    {
        bool ischk = false;
        for (size_t j = 0; j < ignore.length(); j++)
        {
            if (str[i] == ignore[j])
            {
                ischk = true;
            }
        }
        if (ischk)
        {
            if (i == begin)
            {
                begin++;
            }
        }
        else
        {
            end = i + 1;
        }
    }
    if (begin < end)
    {
        str = str.substr(begin, end - begin);
    }
    else
    {
        str.clear();
    }
}

int
log4x::start()
{
    if (active())
    {
        return 0;
    }

    if (Thread::start() < 0)
    {
        return -1;
    }

    return _sem.wait(3000);
}

void
log4x::stop()
{
    Thread::stop();

    {
        AutoLock locker(_pool_mtx);
        while (!_pool.empty())
        {
            log4x_t* log = _pool.back();
            _pool.pop_back();
            delete log;
        }
    }

    {
        AutoLock locker(_cache_mtx);
        while (!_cache.empty())
        {
            log4x_t* log = _cache.back();
            _cache.pop_back();
            delete log;
        }
    }
}

int
log4x::prepush(const char * key, int level)
{
    if (!key || !active())
    {
        return -1;
    }

    {
        AutoLock locker(_keys_mtx);
        std::map<std::string, log4x_value_t*>::const_iterator iter = _keys.find(key);
        if (iter == _keys.end())
        {
            char text[256] = {0};
            snprintf_s(text, sizeof(text), "[%s] do not find the key: %s [%s:%d]\r\n",  __FUNCTION__, key, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);

            _keys[key] = new log4x_value_t();

            _keys[key]->enable = true;
            _keys[key]->key    = key;

            history(_keys[key]->path, key, _keys[key]->history);
        }
        else
        {
            if (level < iter->second->level)
            {
                return -1;
            }
        }
    }

    {
        AutoLock locker(_logs_mtx);
        size_t count = _logs.size();

        if (count > LOG4X_LOG_QUEUE_LIMIT_SIZE)
        {
            char text[256] = {0};
            snprintf_s(text, sizeof(text), "[%s] count > LOG4X_LOG_QUEUE_LIMIT_SIZE [%s:%d]\r\n",  __FUNCTION__, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);

            uint32_t rate = (uint32_t)(count - LOG4X_LOG_QUEUE_LIMIT_SIZE);
            os::thread()->sleep(rate);
        }
    }

    return 0;
}

log4x_t *
log4x::make(const char * key, int level, const char * func, const char * file, int line)
{
    log4x_t * log = NULL;

    {
        AutoLock locker(_pool_mtx);
        if (!_pool.empty())
        {
            log = _pool.back();
            _pool.pop_back();
        }
    }

    if (!log)
    {
        /* log = new (malloc(sizeof(log4x_t) + LOG4X_LOG_BUF_SIZE))log4x_t(); */
        log = new log4x_t();
    }

    log->pts.clear();
    log->text.clear();
    log->pos.clear();

    {
        /* note include <sstream> */
        timeb tb;
        ftime(&tb);

        log->tid   = os::thread()->tid() & 0xFFFF;
        log->key   = key;
        log->level = level;
        log->time  = tb.time;
        log->msec  = tb.millitm;
    }

    tm lt;
    os::time()->localtime(log->time, lt);

    char buf[40] = { 0 };
    snprintf_s(buf, 40, "[%04d-%02d-%02d %02d:%02d:%02d.%03d][%04x][%s]",
               lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
               lt.tm_hour, lt.tm_min, lt.tm_sec,
               log->msec, (unsigned)log->tid, log4x_level[log->level]);
    log->pts = buf;

    log->mode  = "[";
    log->mode += key;
    log->mode += "]";

    if (func && file)
    {
        log->pos  = " [";
        log->pos += func;
        log->pos += ":";
        log->pos += fs::basename(file);
        log->pos += ":";
        log->pos += str::to_str(line);
        log->pos += "]";
    }

    return log;
}

void
log4x::free(log4x_t * log)
{
    if (_pool.size() < 200)
    {
        AutoLock locker(_pool_mtx);
        _pool.push_back(log);
    }
    else
    {
        delete log;
    }
}

int
log4x::push(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, ...)
{
    if (prepush(key, level) < 0)
    {
        return -1;
    }

    log4x_t * log = make(key, level, func, file, line);

    log->text = " ";

    va_list args;
    va_start(args, fmt);
    int estsize = vestimate(fmt, args);
    va_end(args);

    do
    {
        int size   = 1024 * 4;
        int result = -1;
        if (estsize > 0 && estsize < size)
        {
            char buf[1024 * 4] = {0};
            va_start(args, fmt);
            result = vsnprintf(buf, sizeof(buf), fmt, args);
            va_end(args);

            if (result >= 0)
            {
                log->text += buf;
                break;
            }
        }

        if (estsize > 10 * 1024 * 1024)
        {
            char text[256] = { 0 };
            snprintf_s(text, sizeof(text), "[%s] too large input data: %s [%s:%d]\r\n", __FUNCTION__, key, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);
            return -1;
        }

        size = estsize + 100;
        char *p = (char *)::malloc(size);
        if (!p)
        {
            char text[256] = { 0 };
            snprintf_s(text, sizeof(text), "[%s] malloc memery failed: %s [%s:%d]\r\n", __FUNCTION__, key, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);
            return -1;
        }

        va_start(args, fmt);
        result = vsnprintf(p, size, fmt, args);
        va_end(args);

        if (result < 0)
        {
            ::free(p);
            return -1;
        }
        log->text += p;
        ::free(p);
    }
    while (0);

    return push(log, sync);
}

int
log4x::vpush(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, va_list args)
{
    if (prepush(key, level) < 0)
    {
        return -1;
    }

    log4x_t* log = make(key, level, func, file, line);
    log->text = " ";

    int estsize = vestimate(fmt, args);
    do
    {
        int size   = 1024 * 4;
        int result = -1;
        if (estsize > 0 && estsize < size)
        {
            char buf[1024 * 4] = {0};
            result = vsnprintf(buf, sizeof(buf), fmt, args);

            if (result >= 0)
            {
                log->text += buf;
                break;
            }
        }

        if (estsize > 10 * 1024 * 1024)
        {
            char text[256] = { 0 };
            snprintf_s(text, sizeof(text), "[%s] too large input data: %s [%s:%d]\r\n", __FUNCTION__, key, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);
            return -1;
        }

        size = estsize + 100;
        char *p = (char *)::malloc(size);
        if (!p)
        {
            char text[256] = { 0 };
            snprintf_s(text, sizeof(text), "[%s] malloc memery failed: %s [%s:%d]\r\n", __FUNCTION__, key, __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);
            return -1;
        }

        result = vsnprintf(p, size, fmt, args);

        if (result < 0)
        {
            ::free(p);
            return -1;
        }
        log->text += p;
        ::free(p);
    }
    while (0);

    return push(log, sync);
}

int
log4x::push(log4x_t * log, bool sync)
{
    /* discard log */
    std::map<std::string, log4x_value_t*>::iterator iter;
    {
        AutoLock locker(_keys_mtx);
        iter = _keys.find(log->key);
        if (iter == _keys.end())
        {
            char text[256] = { 0 };
            snprintf_s(text, sizeof(text), "[%s] do not find the key: %s [%s:%d]\r\n", __FUNCTION__, log->key.c_str(), __FILE__, __LINE__);
            showColorText(text, LOG_LEVEL_WARN);

            free(log);
            return -1;
        }
    }

    log4x_value_t *value = iter->second;
    if (!active() || !value->enable)
    {
        free(log);
        return -1;
    }

    /* filter log */
    if (log->level < value->level)
    {
        free(log);
        return -1;
    }

    std::string text = value->fileline ? log->pts + log->mode + log->text + log->pos + "\r\n" :
                       log->pts + log->mode + log->text + "\r\n";

    if (_proc.outputsyn || sync)
    {
        if (_proc.outputide)
        {
            showColorText(text.c_str(), log->level);
        }

        if (_proc.outputide)
        {
#ifdef WIN32
            OutputDebugStringA(text.c_str());
#endif
        }

        if (value->outfile)
        {
            {
                AutoLock locker(value->mtx);
                if (0 == openKeyFile(log))
                {
                    value->fhandle.write(text.c_str(), text.length());
                    value->fhandle.flush();
                    value->flen += text.length();
                }
            }

            {
                AutoLock locker(_proc.mtx);
                if (value->procfile && (0 == openProcFile(log)))
                {
                    _proc.fhandle.write(text.c_str(), text.length());
                    _proc.fhandle.flush();
                    _proc.flen += text.length();
                }
            }
        }

        free(log);
        return 0;
    }

    {
        AutoLock locker(_logs_mtx);
        _logs.push_back(log);
    }
    return 0;
}

log4x_t *
log4x::pop()
{
    AutoLock locker(_cache_mtx);
    log4x_t * log = NULL;
    if (_cache.empty())
    {
        if (!_logs.empty())
        {
            if (_logs.empty())
            {
                return NULL;
            }

            AutoLock locker(_logs_mtx);
            _cache.swap(_logs);
        }
    }

    if (!_cache.empty())
    {
        log = _cache.front();
        _cache.pop_front();

        return log;
    }
    return NULL;
}


void
log4x::printNonBlock(const char* fmt, ...)
{
#ifdef __linux__
    if (1 == ::getppid())
    {
        return;
    }
#endif

    va_list args;
    va_start(args, fmt);
    ::vprintf(fmt, args);
    va_end(args);
}

inline void
log4x::showColorText(const char *text, int level)
{
    if (level < LOG_LEVEL_DEBUG || level > LOG_LEVEL_FATAL)
    {
        printNonBlock("%s", text);
        return;
    }
#ifdef WIN32
    AutoLock locker(_show_mtx);
    HANDLE std = ::GetStdHandle(STD_OUTPUT_HANDLE);
    if (std == INVALID_HANDLE_VALUE)
    {
        return;
    }
    CONSOLE_SCREEN_BUFFER_INFO oldInfo;
    if (!GetConsoleScreenBufferInfo(std, &oldInfo))
    {
        return;
    }
    else
    {
        SetConsoleTextAttribute(std, log4x_color[level]);
        printNonBlock("%s", text);
        SetConsoleTextAttribute(std, oldInfo.wAttributes);
    }
#else
    printNonBlock("%s%s\e[0m", log4x_color[level], text);
#endif
    return;
}

int log4x::openKeyFile(log4x_t * log)
{
    {
        std::map<std::string, log4x_value_t*>::const_iterator iter = _keys.find(log->key);
        if (iter == _keys.end())
        {
            showColorText("log4x: can not open, invalide log id!\r\n", LOG_LEVEL_FATAL);
            return -1;
        }
    }

    log4x_value_t *value = _keys[log->key];

    if (!value->enable || !value->outfile || log->level < value->level)
    {
        return -1;
    }

    tm lt, ct;
    os::time()->localtime(log->time, lt);
    os::time()->localtime(value->ftime, ct);
    bool sameday = (
                       (lt.tm_year == ct.tm_year) &&
                       (lt.tm_mon  == ct.tm_mon)  &&
                       (lt.tm_mday == ct.tm_mday)
                   );

    bool newfile = value->flen > (size_t)(value->limitsize * 1024 * 1024);
    if (!sameday || newfile)
    {
        if (!sameday)
        {
            value->findex = 0;
        }
        else
        {
            value->findex++;
        }

        if (value->fhandle.isOpen())
        {
            value->fhandle.close();
        }
    }

    if (value->fhandle.isOpen())
    {
        return 0;
    }

    value->ftime = log->time;
    value->flen  = 0;

    std::string  key = value->key;
    std::string path = value->path;

    char buf[256] = {0};
    if (value->monthdir)
    {
        snprintf_s(buf, sizeof(buf), "%04d_%02d/", lt.tm_year + 1900, lt.tm_mon + 1);
        path += buf;
    }

    if (!fs::is_dir(path))
    {
        AutoLock locker(_keys_mtx);
        if (fs::mk_dir(path) < 0)
        {
            snprintf_s(buf, sizeof(buf), "log4x: can not create directory %s. \r\n", path.c_str());
            showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
            showColorText(buf, LOG_LEVEL_FATAL);
            showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
            return -1;
        }
    }

    snprintf_s(buf, sizeof(buf), "%s_%s_%s_%03u_%04d%02d%02d%02d%02d%02d.log",
               _proc.name.c_str(),  _proc.pid.c_str(), key.c_str(), value->findex, lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
               lt.tm_hour, lt.tm_min, lt.tm_sec);
    path += buf;

    long flen = value->fhandle.open(path.c_str(), "ab");
    if (!value->fhandle.isOpen())
    {
        snprintf_s(buf, sizeof(buf), "log4x: can not open log file %s. \r\n", path.c_str());
        showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
        showColorText(buf, LOG_LEVEL_FATAL);
        showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
        value->outfile = false;
        return -1;
    }

    value->flen = (flen < 0) ? 0 : flen;

#if 0
    if (value->reserve > 0)
    {
        if ((int)value->history.size() >= value->filecount)
        {
            while (!value->history.empty() && value->history.begin()->first < time(NULL) - value->reserve)
            {
                value->fhandle.removeFile(value->history.begin()->second.c_str());
                value->history.erase(value->history.begin());
            }
        }
    }
#else
    /* 对于每个key, 只保留LOG4X_DEFAULT_RESERVE_FILES个文件, 不判断时间 */
    while ((int)value->history.size() >= value->filecount)
    {
        value->fhandle.removeFile(value->history.begin()->second.c_str());
        value->history.erase(value->history.begin());
    }
#endif

    if (value->history.empty() || value->history.rbegin()->second != path)
    {
        value->history[::time(NULL)] =  path;
    }

    return 0;
}

int log4x::openProcFile(log4x_t * log)
{
    const auto iter = _keys.find(log->key);
    if (iter == _keys.end())
    {
        showColorText("log4x: can not open, invalide log key! \r\n", LOG_LEVEL_FATAL);
        return -1;
    }

    log4x_value_t* value = _keys[log->key];
    if (!value->enable || !value->outfile
            || !value->procfile || log->level < value->level)
    {
        return -1;
    }

    tm lt, ct;
    os::time()->localtime(log->time, lt);
    os::time()->localtime(_proc.ftime, ct);
    bool sameday = (
                       (lt.tm_year == ct.tm_year) &&
                       (lt.tm_mon  == ct.tm_mon)  &&
                       (lt.tm_mday == ct.tm_mday)
                   );

    bool newfile = _proc.flen > (size_t)(_proc.limitsize * 1024 * 1024);
    if (!sameday || newfile)
    {
        if (!sameday)
        {
            _proc.findex = 0;
        }
        else
        {
            _proc.findex++;
        }

        if (_proc.fhandle.isOpen())
        {
            _proc.fhandle.close();
        }
    }

    if (_proc.fhandle.isOpen())
    {
        return 0;
    }

    _proc.ftime = log->time;
    _proc.flen  = 0;

    string path = _proc.path;
    char buf[256] = {0};
    if (_proc.monthdir)
    {
        snprintf_s(buf, sizeof(buf), "%04d_%02d/", lt.tm_year + 1900, lt.tm_mon + 1);
        path += buf;
    }

    if (!fs::exists(path))
    {
        AutoLock locker(_keys_mtx);
        if (fs::mk_dir(path) < 0)
        {
            snprintf_s(buf, sizeof(buf), "log4x: can not create directory %s. \r\n", path.c_str());
            showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
            showColorText(buf, LOG_LEVEL_FATAL);
            showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
            return -1;
        }
    }

    snprintf_s(buf, sizeof(buf), "%s_%s_%03u_%04d%02d%02d%02d%02d%02d.log",
               _proc.name.c_str(), _proc.pid.c_str(), _proc.findex, lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
               lt.tm_hour, lt.tm_min, lt.tm_sec);
    path += buf;

    long flen = _proc.fhandle.open(path.c_str(), "ab");
    if (!_proc.fhandle.isOpen())
    {
        snprintf_s(buf, sizeof(buf), "log4x: can not open proess log file %s. \r\n", path.c_str());
        showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);
        showColorText(buf, LOG_LEVEL_FATAL);
        showColorText("!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n", LOG_LEVEL_FATAL);

        return 0;
    }

    _proc.flen = flen < 0 ? 0 : flen;

#if 0
    if ((int)_proc.history.size() >= _proc.filecount)
    {
        while (!_proc.history.empty() && _proc.history.begin()->first < time(NULL) - _proc.reserve)
        {
            _proc.fhandle.removeFile(_proc.history.begin()->second.c_str());
            _proc.history.erase(_proc.history.begin());
        }
    }
#else

    /* 对于每个proc, 只保留LOG4X_DEFAULT_RESERVE_FILES个文件, 不判断时间 */
    while ((int)_proc.history.size() >= _proc.filecount)
    {
        _proc.fhandle.removeFile(_proc.history.begin()->second.c_str());
        _proc.history.erase(_proc.history.begin());
    }
#endif

    if (_proc.history.empty() || _proc.history.rbegin()->second != path)
    {
        _proc.history[::time(NULL)] = path;
    }

    return 0;
}

void log4x::close(const std::string &key)
{
    {
        std::map<std::string, log4x_value_t*>::const_iterator iter = _keys.find(key);
        if (iter == _keys.end())
        {
            showColorText("log4x: can not close, invalide log key!\r\n", LOG_LEVEL_FATAL);

            return;
        }
    }

    log4x_value_t *value = _keys[key];
    if (value->fhandle.isOpen())
    {
        value->fhandle.close();
    }
}

void
log4x::run()
{
    LOGI("log4x thread started! " << "VERSION: " VERSION_TAG "-" VERSION_SHA1 ", BUILD: " VERSION_BUILD_PTS);

    for (std::map<std::string, log4x_value_t*>::const_iterator iter = _keys.begin(); iter != _keys.end(); ++iter)
    {
        if (_keys[iter->first]->enable)
        {
            LOGI("key: " << _keys[iter->first]->key << "\t"
                 << "path: " << _keys[iter->first]->path << "\t"
                 << "level: " << _keys[iter->first]->level << "\t"
                 << "display: " << _keys[iter->first]->display);
        }
    }

    _sem.post();

    log4x_t * log     = NULL;
    time_t    lastupd = ::time(NULL);

    std::map<std::string, bool> fflags;

    while (true)
    {
        while ((log = pop()))
        {
            std::map<std::string, log4x_value_t*>::iterator iter;
            {
                AutoLock locker(_keys_mtx);
                iter = _keys.find(log->key);
                if (iter == _keys.end())
                {
                    free(log);
                    continue;
                }
            }

            log4x_value_t *value = iter->second;

            /* discard */
            if (!value->enable || log->level < value->level)
            {
                free(log);
                continue;
            }

            std::string text = value->fileline ? log->pts + log->mode + log->text + log->pos + "\r\n" :
                               log->pts + log->mode + log->text + "\r\n";
            if (value->display)
            {
                showColorText(text.c_str(), log->level);
            }

            if (_proc.outputide)
            {
#ifdef WIN32
                OutputDebugStringA(text.c_str());
#endif
            }

            if (value->outfile)
            {
                AutoLock locker(value->mtx);
                {
                    if (openKeyFile(log) < 0)
                    {
                        free(log);
                        continue;
                    }
                }

                value->fhandle.write(text.c_str(), text.length());
                value->flen += text.length();
                value->flushes++;

                {
                    int res = -1;
                    {
                        AutoLock locker(_proc.mtx);
                        res = openProcFile(log);
                    }

                    if (value->procfile && (0 == res))
                    {
                        _proc.fhandle.write(text.c_str(), text.length());
                        _proc.flen += text.length();
                        _proc.flushes++;
                    }
                }
            }
            else
            {
                /* _ullStatusTotalWriteFileCount++; */
                /* _ullStatusTotalWriteFileBytes += log->len; */
            }

            free(log);
        }

        {
            AutoLock locker(_keys_mtx);
            for (std::map<std::string, log4x_value_t*>::iterator iter = _keys.begin(); iter != _keys.end(); ++iter)
            {
                if (iter->second->enable && iter->second->flushes > 0)
                {
                    iter->second->fhandle.flush();
                    iter->second->flushes = 0;
                }

                if (!iter->second->enable && iter->second->fhandle.isOpen())
                {
                    iter->second->fhandle.close();
                }
            }
        }

        if (_proc.flushes > 0)
        {
            _proc.fhandle.flush();
            _proc.flushes = 0;
        }

        if (!active() && _logs.empty())
        {
            break;
        }

        if (_hotInterval != 0 && (::time(NULL) - lastupd) > _hotInterval)
        {
            update();
            lastupd = ::time(NULL);
        }

        /* std::this_thread::sleep_for(std::chrono::milliseconds(50)); */
        sleep(50);
    }
}

int
log4x::enable(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    /* The lock has been locked in LOGE, and it will deadlock */
    /* AutoLock locker(_keys_mtx); */

    /* The log4x_value_t of this key will added after called LOG_WARN */
    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->enable = enable;

    return 0;
}

int
log4x::setPath(const char * key, const char * path)
{
    if (!key || !path)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", path: " << path);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->path = path;

    return 0;
}

int
log4x::setLevel(const char * key, int level)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    if (level < LOG_LEVEL_TRACE)
    {
        LOGW("invalid level value: " << level);
        level = LOG_LEVEL_TRACE;
    }

    if (level > LOG_LEVEL_FATAL)
    {
        LOGW("invalid level value: " << level);
        level = LOG_LEVEL_FATAL;
    }

    LOG_FATAL(key, "the key: " << key << ", level: " << level);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->level = level;

    return 0;
}

int
log4x::setFileLine(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->fileline = enable;

    return 0;
}

int
log4x::setDisplay(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->display = enable;

    return 0;
}

int
log4x::setOutFile(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }

    iter->second->outfile = enable;

    return 0;
}

int
log4x::setLimitsize(const char * key, unsigned int limitsize)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    if (limitsize < 1)
    {
        LOGW("invalid limitsize value: " << limitsize);
        limitsize = 1;
    }

    if (limitsize > LOG4X_DEFAULT_LIMITSIZE)
    {
        LOGW("invalid limitsize value: " << limitsize);
        limitsize = LOG4X_DEFAULT_LIMITSIZE;
    }

    LOG_FATAL(key, "the key: " << key << ", limit size: " << limitsize);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }
    iter->second->limitsize = limitsize;

    return 0;
}

int
log4x::setMonthdir(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }
    iter->second->monthdir = enable;

    return 0;
}

int
log4x::setReserve(const char * key, unsigned int sec)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    if (sec < 30)
    {
        LOGW("invalid sec value: " << sec);
        sec = 30;
    }

    if (sec > LOG4X_DEFAULT_RESERVE * 7)
    {
        LOGW("invalid sec value: " << sec);
        sec = LOG4X_DEFAULT_RESERVE * 7;
    }

    LOG_FATAL(key, "the key: " << key << ", second: " << sec);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }
    iter->second->reserve = sec;

    return 0;
}

int
log4x::setProcFile(const char * key, bool enable)
{
    if (!key)
    {
        LOGE("invalid args");
        return -1;
    }

    LOG_FATAL(key, "the key: " << key << ", enable: " << enable);

    AutoLock locker(_keys_mtx);
    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return -1;
    }
    iter->second->procfile = enable;

    return 0;
}

void
log4x::setAutoUpdate(int interval)
{
    _hotInterval = interval;
}

int
log4x::update()
{
    if (_path.empty())
    {
        return -1;
    }

    File f;
    f.open(_path.c_str(), "rb");
    if (!f.isOpen())
    {
        LOGW("log4x load config file failed, filename: " << _path);
        return -1;
    }

    return parse(f.readContent());
}

bool
log4x::isff(const char * key)
{
    if (!key)
    {
        LOGE("invalid args");
        return false;
    }

    std::map<std::string, log4x_value_t*>::iterator iter = _keys.find(key);
    if (iter == _keys.end())
    {
        return false;
    }

    return iter->second->fromfile;
}

inline int
log4x::history(const std::string &path, const std::string &key, log4x_history_t &files)
{
    tm lt;
    time_t tt = ::time(NULL);
    os::time()->localtime(tt, lt);

    std::string fpath   = path;
    char        buf[40] = {0};

    if (("global" == key && _proc.monthdir) ||
            _keys[key]->monthdir)
    {
        snprintf_s(buf, 40, "%04d_%02d/", lt.tm_year + 1900, lt.tm_mon + 1);
        fpath += buf;
    }

    vector<string> ls;
    if (fs::ls_dir(fpath, ls) < 0)
    {
        return -1;
    }

    for (size_t i = 0; i < ls.size(); ++i)
    {
        string name = ls[i];
        std::string::size_type pos = std::string::npos;

        if (name.length() < 30)
        {
            continue;
        }

        if (std::string::npos == name.find(_proc.name))
        {
            continue;
        }

        name = str::gsub(name, _proc.name + "_", "");
        if ("global" != key)
        {
            if (std::string::npos == name.find(key))
            {
                continue;
            }
            name = str::gsub(name, key + "_", "");
        }
 
        vector<string> parts;
        if (str::split(name, "_", parts) < 0 || parts.size() != 3)
        {
            continue;
        }

        string pts = parts[2];
        string sdt = pts.substr(0, 4) + "-" + pts.substr(4, 2) + "-" + pts.substr(6, 2) + " " +
        pts.substr(8, 2) + ":" +  pts.substr(10, 2) + ":" + pts.substr(12, 2);
        time_t tt  = time::strptime(sdt);
        files[tt] = fpath + ls[i];
    }

    return 0;
}

/**
 * ilog4x
 */
ilog4x *
ilog4x::instance()
{
    static log4x log;
    return &log;
}

ilog4x &
ilog4x::reference()
{
    return *instance();
}

int
vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
    int result = ::vsnprintf(str, size, format, ap);
#ifdef _WIN32
    return result;
#else
    if (result > (int)size)
    {
        return -1;
    }
    return result;
#endif
}

int
vestimate(const char *format, va_list ap)
{
    if (format == NULL)
    {
        return -1;
    }

    char *fmt   = (char *)format;
    int   total = (int)strlen(fmt);

    while (*fmt)
    {
        if (*fmt++ == '%')
        {
            total--;
            while (*fmt == '.' || *fmt == '-' || *fmt == 'l' || (*fmt >= '0' && *fmt <= '9'))
            {
                ++fmt;
                /* estimate 20 chars */
                total += 20;
            }

            switch (*fmt++)
            {
            case 0:
                /* Malformed format string.. */
                --fmt;
                break;

            case 'c':
            case 'i':
            case 'd':
            case 'u':
            case 'o':
            case 'x':
            case 'X':
            {
                int i = va_arg(ap, int);
                total += sizeof(i);
            }
            break;

            case 'f':
            {
                double f = va_arg(ap, double);
                total += sizeof(f);
            }
            break;

            case 'p':
            {
                void *p = va_arg(ap, void *);
                total += sizeof(p);
            }
            break;

            case 's':
            {
                const char *s = va_arg(ap, const char *);
                if (!s)
                {
                    break;
                }
                total += (int)strlen(s);
            }
            break;

            default:
                break;
            }
            total--;
        }
    }

    return  total;
}

int
vpush(const char * key, int level, const char * func, const char * file, int line, bool sync, const char* fmt, va_list ap)
{
    return ((log4x *)ilog4x::instance())->vpush(key, level, func, file, line, sync, fmt, ap);
}

LOG4X_API int
format(char *dest, int n, const char* fmt, ...)
{
    if (!dest || n <= 0)
    {
        LOGFE("invalid args");
        return -1;
    }

    va_list arg;
    va_start(arg, fmt);
    vsnprintf(dest, (size_t)n, fmt, arg);
    va_end(arg);

    return 0;
}

}
